/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { AbstractFieldRef, AbstractInvokeExpr, ArkAssignStmt, ArkAwaitExpr, ArkField, ArkFile, ArkInstanceFieldRef, ArkInstanceInvokeExpr, ArkInvokeStmt, ArkMethod, ArkNamespace, ArkPtrInvokeExpr, ArkStaticFieldRef, ClassSignature, DEFAULT_ARK_CLASS_NAME, FunctionType, Local, MethodSignature, Scene, Stmt, Type, UnionType, UnknownType, Value } from 'arkanalyzer';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { CheckerUtils, Defects, MatcherCallback, Rule } from '../../Index';
import { IssueReport } from '../../model/Defects';

const multimediaAPI8CreateSignList: string[] = [
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.${DEFAULT_ARK_CLASS_NAME}.createAudioRenderer(@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRendererOptions, @ohosSdk/api/@ohos.base.d.ts: AsyncCallback<@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRenderer>)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.${DEFAULT_ARK_CLASS_NAME}.createAudioRenderer(@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRendererOptions, @ohosSdk/api/@ohos.base.d.ts: AsyncCallback<@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRenderer,void>)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.${DEFAULT_ARK_CLASS_NAME}.createAudioRenderer(@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRendererOptions)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.${DEFAULT_ARK_CLASS_NAME}.createAudioCapturer(@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturerOptions, @ohosSdk/api/@ohos.base.d.ts: AsyncCallback<@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturer>)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.${DEFAULT_ARK_CLASS_NAME}.createAudioCapturer(@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturerOptions)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.${DEFAULT_ARK_CLASS_NAME}.createAudioCapturer(@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturerOptions, @ohosSdk/api/@ohos.base.d.ts: AsyncCallback<@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturer,void>)`
];
const multimediaInterruptSignList: string[] = [
    `@ohosSdk/api/@ohos.multimedia.media.d.ts: media.AVPlayer.play(@ohosSdk/api/@ohos.base.d.ts: AsyncCallback<void,void>)`,
    `@ohosSdk/api/@ohos.multimedia.media.d.ts: media.AVPlayer.play()`,
];
const multimediaTypeList: string[] = [
    `@ohosSdk/api/@ohos.multimedia.media.d.ts: media.AVPlayer`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRenderer`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturer`
];
const stopSignature: string[] = [
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRenderer.stop(@ohosSdk/api/@ohos.base.d.ts: AsyncCallback<void,void>)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRenderer.stop()`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRenderer.pause(@ohosSdk/api/@ohos.base.d.ts: AsyncCallback<void,void>)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioRenderer.pause()`,
    `@ohosSdk/api/@ohos.multimedia.media.d.ts: media.AVPlayer.stop(@ohosSdk/api/@ohos.base.d.ts: AsyncCallback<void,void>)`,
    `@ohosSdk/api/@ohos.multimedia.media.d.ts: media.AVPlayer.stop()`,
    `@ohosSdk/api/@ohos.multimedia.media.d.ts: media.AVPlayer.pause(@ohosSdk/api/@ohos.base.d.ts: AsyncCallback<void,void>)`,
    `@ohosSdk/api/@ohos.multimedia.media.d.ts: media.AVPlayer.pause()`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturer.stop(@ohosSdk/api/@ohos.base.d.ts: AsyncCallback<void,void>)`,
    `@ohosSdk/api/@ohos.multimedia.audio.d.ts: audio.AudioCapturer.stop()`
];
const backgroundSignature: string[] = [
    `@ohosSdk/api/@ohos.resourceschedule.backgroundTaskManager.d.ts: backgroundTaskManager.${DEFAULT_ARK_CLASS_NAME}.startBackgroundRunning(@ohosSdk/api/application/BaseContext.d.ts: BaseContext, @ohosSdk/api/@ohos.resourceschedule.backgroundTaskManager.d.ts: backgroundTaskManager.BackgroundMode, @ohosSdk/api/@ohos.wantAgent.d.ts: ${DEFAULT_ARK_CLASS_NAME}.[static]${DEFAULT_ARK_CLASS_NAME}()#WantAgent, @ohosSdk/api/@ohos.base.d.ts: AsyncCallback<void,void>)`,
    `@ohosSdk/api/@ohos.resourceschedule.backgroundTaskManager.d.ts: backgroundTaskManager.${DEFAULT_ARK_CLASS_NAME}.startBackgroundRunning(@ohosSdk/api/application/BaseContext.d.ts: BaseContext, @ohosSdk/api/@ohos.resourceschedule.backgroundTaskManager.d.ts: backgroundTaskManager.BackgroundMode, @ohosSdk/api/@ohos.wantAgent.d.ts: ${DEFAULT_ARK_CLASS_NAME}.[static]${DEFAULT_ARK_CLASS_NAME}()#WantAgent)`,
    `@ohosSdk/api/@ohos.resourceschedule.backgroundTaskManager.d.ts: backgroundTaskManager.${DEFAULT_ARK_CLASS_NAME}.startBackgroundRunning(@ohosSdk/api/application/BaseContext.d.ts: BaseContext, string[], @ohosSdk/api/@ohos.wantAgent.d.ts: ${DEFAULT_ARK_CLASS_NAME}.[static]${DEFAULT_ARK_CLASS_NAME}()#WantAgent)`
];
const multimediaInterruptStmtList: Set<Stmt> = new Set();
const multimediaCreateList: CreateMultimediaInfo[] = [];
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ReasonableAudioUseCheck');
const gMetaData: BaseMetaData = {
    severity: 3,
    ruleDocPath: 'docs/reasonable-audio-use-check.md',
    description: 'Ensure applications without continuous tasks release audio resources when moved to the background.'
};

interface CreateMultimediaInfo {
    methodName: string;
    createStmt: Stmt;
    varInfo: Local | null;
    fieldInfo: ArkField | null;
}

enum UseType {
    CALLBACK,
    PROMISE_THEN,
    PROMISE_AWIT,
    UNKNOWN
}

export class ReasonableAudioUseCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public issues: IssueReport[] = [];

    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: undefined,
            callback: this.check
        };
        return [matchBuildCb];
    }

    public check = (scene: Scene): void => {
        for (let arkFile of scene.getFiles()) {
            this.processClass(arkFile);
        }
        this.commonInvokerMatch();
        for (let cmi of multimediaCreateList) {
            if (!cmi.varInfo) {
                let targetArkFile = cmi.createStmt.getCfg()?.getDeclaringMethod().getDeclaringArkFile();
                if (!targetArkFile) {
                    continue;
                }
                this.reportIssue(targetArkFile, cmi.createStmt, cmi.methodName);
            }
        }
    };

    private processClass(arkFile: ArkFile): void {
        for (let clazz of arkFile.getClasses()) {
            for (let mtd of clazz.getMethods()) {
                if (this.hasBackgroundMethodCall(mtd)) {
                    return;
                }
            }
            let foregroundMethod = clazz.getMethodWithName('onForeground');
            if (!foregroundMethod) {
                continue;
            }
            this.processArkMethod(arkFile, foregroundMethod);
        }
        for (let namespace of arkFile.getAllNamespacesUnderThisFile()) {
            this.processNamespace(arkFile, namespace);
        }
    }

    private processNamespace(arkFile: ArkFile, namespace: ArkNamespace): void {
        for (let clazz of namespace.getClasses()) {
            for (let mtd of clazz.getMethods()) {
                if (this.hasBackgroundMethodCall(mtd)) {
                    return;
                }
            }
            let foregroundMethod = clazz.getMethodWithName('onForeground');
            if (!foregroundMethod) {
                continue;
            }
            this.processArkMethod(arkFile, foregroundMethod);
        }
    }

    private hasBackgroundMethodCall(method: ArkMethod, checkedMethods = new Set<string>()): boolean {
        let stmts = method.getBody()?.getCfg().getStmts();
        if (!stmts) {
            return false;
        }
        for (let stmt of stmts) {
            if (!(stmt instanceof ArkInvokeStmt)) {
                continue;
            }
            let invoker = stmt.getInvokeExpr();
            let signature = invoker.getMethodSignature().toString();
            if (backgroundSignature.includes(signature)) {
                return true;
            }
            if (!checkedMethods.has(signature)) {
                checkedMethods.add(signature);
                let invokedMethod = method.getDeclaringArkFile().getScene().getMethod(invoker.getMethodSignature());
                if (invokedMethod && this.hasBackgroundMethodCall(invokedMethod, checkedMethods)) {
                    return true;
                }
            }
        }
        return false;
    }

    private hasMethodSignatureInStatements(method: ArkMethod, baseName: string): boolean {
        const stmts = method.getBody()?.getCfg().getStmts();
        if (!stmts) {
            return false;
        }
        const stack = [{ method, stmts: stmts }];
        while (stack.length > 0) {
            const { method, stmts } = stack.pop()!;
            for (const stmt of stmts) {
                const invoker = CheckerUtils.getInvokeExprFromStmt(stmt);
                if (!invoker) {
                    continue;
                }
                if (!(invoker instanceof ArkInstanceInvokeExpr)) {
                    continue;
                }
                if (stopSignature.includes(invoker.getMethodSignature().toString()) && invoker.getBase().getName() === baseName) {
                    return true;
                }
                const invMethod = method.getDeclaringArkFile().getScene().getMethod(invoker.getMethodSignature());
                if (invMethod) {
                    stack.push({ method: invMethod, stmts: invMethod.getBody()?.getCfg().getStmts() || [] });
                }
            }
        }
        return false;
    }

    private commonInvokerMatch(): void {
        for (let cmi of multimediaCreateList) {
            if (cmi.varInfo || cmi.methodName !== 'play') {
                continue;
            }
            let stmt = cmi.createStmt;
            let invoker = stmt.getInvokeExpr();
            if (!invoker || !(invoker instanceof ArkInstanceInvokeExpr)) {
                continue;
            }
            if (this.isCallbackMethodBackground(stmt.getCfg().getDeclaringMethod(), invoker.getBase())) {
                cmi.varInfo = invoker.getBase();
            }
        }
    }

    private processArkMethod(arkFile: ArkFile, method: ArkMethod): void {
        const busyMethods: Set<MethodSignature> = new Set();
        const stmts = method.getBody()?.getCfg()?.getStmts();
        if (!stmts) {
            return;
        }

        for (let i = 0; i < stmts.length; i++) {
            const stmt = stmts[i];
            this.processStmt(arkFile, method, stmt, busyMethods);
        }
    }

    private processStmt(arkFile: ArkFile, method: ArkMethod, stmt: Stmt, busyMethods: Set<MethodSignature>): void {
        const invoker = CheckerUtils.getInvokeExprFromStmt(stmt);
        if (!invoker) {
            return;
        }
        const methodSignature = invoker.getMethodSignature();
        const methodName = methodSignature.getMethodSubSignature().getMethodName();
        if (!this.isMultimediaCreateStmt(stmt)) {
            return;
        }
        const createInfo: CreateMultimediaInfo = {
            methodName: methodName,
            createStmt: stmt,
            varInfo: null,
            fieldInfo: null,
        };
        multimediaCreateList.push(createInfo);
        if (createInfo.methodName === 'play') {
            multimediaInterruptStmtList.add(stmt);
            return;
        }
        if (stmt instanceof ArkInvokeStmt) {
            this.processInvokeStmt(arkFile, methodSignature, stmt, createInfo, busyMethods);
        } else if (stmt instanceof ArkAssignStmt) {
            this.processAssignStmt(arkFile, method, stmt, createInfo, busyMethods);
        }
    }

    private processInvokeStmt(arkFile: ArkFile, methodSignature: MethodSignature, stmt: ArkInvokeStmt,
        createInfo: CreateMultimediaInfo, busyMethods: Set<MethodSignature>): void {
        const callbackMethod = this.getInvokeCallbackMethod(arkFile, stmt, createInfo.methodName);
        if (!callbackMethod) {
            return;
        }
        busyMethods.add(methodSignature);
        this.processCallbackMethod(arkFile, callbackMethod, 0, createInfo, busyMethods, UseType.CALLBACK);
    }

    private processAssignStmt(arkFile: ArkFile, method: ArkMethod, stmt: ArkAssignStmt,
        createInfo: CreateMultimediaInfo, busyMethods: Set<MethodSignature>): void {
        const leftOp = stmt.getLeftOp();
        if (this.isAwaitReturnStmt(stmt)) {
            this.processCallbackMethod(arkFile, method, 0, createInfo, busyMethods, UseType.PROMISE_AWIT);
            return;
        }
        const thenCallbackMethod = this.getPromiseThenCallbackMethod(arkFile, leftOp);
        if (!thenCallbackMethod) {
            return;
        }
        busyMethods.add(thenCallbackMethod.getSignature());
        this.processCallbackMethod(arkFile, thenCallbackMethod, 0, createInfo, busyMethods, UseType.PROMISE_THEN);
    }

    private isAwaitReturnStmt(stmt: ArkAssignStmt): boolean {
        let leftOp = stmt.getLeftOp();
        if (!(leftOp instanceof Local)) {
            return false;
        }
        if (!leftOp.getName().includes('%')) {
            return false;
        }
        let usedStmts = leftOp.getUsedStmts();
        for (let usedStmt of usedStmts) {
            if (!(usedStmt instanceof ArkAssignStmt)) {
                continue;
            }
            let rightOp = usedStmt.getRightOp();
            if (rightOp instanceof ArkAwaitExpr) {
                return true;
            }
        }
        return false;
    }

    private getPromiseThenCallbackMethod(arkFile: ArkFile, leftOp: Value): ArkMethod | null {
        if (!(leftOp instanceof Local)) {
            return null;
        }
        let usedStmts = leftOp.getUsedStmts();
        if (usedStmts.length === 0) {
            return null;
        }
        let firstStmt = usedStmts[0];
        let thenInvoker = CheckerUtils.getInvokeExprFromStmt(firstStmt);
        if (!thenInvoker) {
            return null;
        }
        let methodSignature = thenInvoker.getMethodSignature();
        let thenMethodName = methodSignature.getMethodSubSignature().getMethodName();
        if (thenMethodName !== 'then') {
            return null;
        }
        let args = thenInvoker.getArgs();
        if (args.length === 0) {
            return null;
        }
        let thenArg = args[0];
        if (!(thenArg instanceof Local)) {
            return null;
        }
        let thenArgType = thenArg.getType();
        if (!(thenArgType instanceof FunctionType)) {
            return null;
        }
        return arkFile.getScene().getMethod(thenArgType.getMethodSignature());
    }

    private processCallbackMethod(arkFile: ArkFile, callbackMethod: ArkMethod, index: number,
        createInfo: CreateMultimediaInfo, busyMethods: Set<MethodSignature>, useType: UseType): void {
        const stmts = callbackMethod.getBody()?.getCfg()?.getStmts();
        if (!stmts) {
            return;
        }
        for (let i = index; i < stmts.length; i++) {
            const stmt = stmts[i];
            this.processStatement(arkFile, callbackMethod, stmt, createInfo, busyMethods, useType);
        }
    }

    private processStatement(arkFile: ArkFile, callbackMethod: ArkMethod, stmt: Stmt,
        createInfo: CreateMultimediaInfo, busyMethods: Set<MethodSignature>, useType: UseType): void {
        if (stmt instanceof ArkAssignStmt && this.isInstanceAssignToVariable(callbackMethod, stmt, useType)) {
            this.parseRealAttachInstance(callbackMethod, stmt, createInfo, useType);
        } else if (stmt instanceof ArkInvokeStmt && this.isResolveAssignToVariable(arkFile, stmt, useType)) {
            this.processInvokeStatement(callbackMethod, stmt, createInfo);
        }

        const invoker = CheckerUtils.getInvokeExprFromStmt(stmt);
        if (invoker) {
            this.processInvoker(arkFile, callbackMethod, invoker, createInfo, busyMethods, useType);
        }
    }

    private processInvokeStatement(callbackMethod: ArkMethod, stmt: ArkInvokeStmt, createInfo: CreateMultimediaInfo): void {
        const invokeExpr = stmt.getInvokeExpr();
        if (!(invokeExpr instanceof ArkPtrInvokeExpr)) {
            return;
        }

        const ptr = invokeExpr.getFuncPtrLocal();
        if (!(ptr instanceof ArkInstanceFieldRef)) {
            return;
        }

        const base = this.getFieldByBase(ptr.getBase());
        if (base) {
            if (base instanceof Local && this.isCallbackMethodBackground(callbackMethod, base)) {
                createInfo.varInfo = base;
            } else if (base instanceof ArkField && this.isCallbackMethodBackground(callbackMethod, base)) {
                createInfo.varInfo = ptr.getBase();
                createInfo.fieldInfo = base;
            }
        }
    }

    private processInvoker(arkFile: ArkFile, callbackMethod: ArkMethod, invoker: AbstractInvokeExpr,
        createInfo: CreateMultimediaInfo, busyMethods: Set<MethodSignature>, useType: UseType): void {
        if (this.isAudioInterruptSignature(arkFile, invoker)) {
            if (invoker instanceof ArkInstanceInvokeExpr && this.isCallbackMethodBackground(callbackMethod, invoker.getBase())) {
                createInfo.varInfo = invoker.getBase();
            }
            return;
        }

        for (const arg of invoker.getArgs()) {
            if (!this.isArgTypeMultimedia(callbackMethod, arg, useType)) {
                continue;
            }

            const methodSignature = invoker.getMethodSignature();
            if (busyMethods.has(methodSignature)) {
                continue;
            }

            busyMethods.add(methodSignature);
            const invokeChainMethod = arkFile.getScene().getMethod(methodSignature);
            if (invokeChainMethod) {
                this.processCallbackMethod(arkFile, invokeChainMethod, 0, createInfo, busyMethods, UseType.CALLBACK);
            }
        }
    }

    private isCallbackMethodBackground(callbackMethod: ArkMethod, val: Local | ArkField): boolean {
        let backgroundMethod = callbackMethod.getDeclaringArkClass().getMethodWithName('onBackground');
        if (!backgroundMethod) {
            return false;
        }
        return this.hasMethodSignatureInStatements(backgroundMethod, val.getName());
    }

    private parseRealAttachInstance(callbackMethod: ArkMethod, stmt: ArkAssignStmt,
        createInfo: CreateMultimediaInfo, useType: UseType): void {
        let leftOp = stmt.getLeftOp();
        if (leftOp instanceof Local) {
            if (this.isCallbackMethodBackground(callbackMethod, leftOp)) {
                createInfo.varInfo = leftOp;
            }
            let useStmts = leftOp.getUsedStmts();
            for (let usedStmt of useStmts) {
                if (!(usedStmt instanceof ArkAssignStmt)) {
                    continue;
                }
                let usedLeftOp = usedStmt.getLeftOp();
                let usedRightOp = usedStmt.getRightOp();
                if (usedRightOp instanceof Local && usedRightOp.getName() === leftOp.getName()) {
                    this.parseRealAttachInstance(callbackMethod, usedStmt, createInfo, useType);
                } else if (usedLeftOp instanceof Local && usedRightOp instanceof ArkAwaitExpr &&
                    this.isCallbackMethodBackground(callbackMethod, usedLeftOp)) {
                    createInfo.varInfo = usedLeftOp;
                }
            }
        } else if (leftOp instanceof AbstractFieldRef) {
            createInfo.fieldInfo = callbackMethod.getDeclaringArkClass().getField(leftOp.getFieldSignature());
        } else {
            logger.debug('Process callback method unknown type.');
        }
    }

    private isArgTypeMultimedia(callbackMethod: ArkMethod, arg: Value, useType: UseType): boolean {
        if (!(arg instanceof Local)) {
            return false;
        }
        let argType = arg.getType();
        if (multimediaTypeList.includes(argType.toString())) {
            return true;
        }
        if (argType instanceof UnionType) {
            for (let type of argType.getTypes()) {
                if (multimediaTypeList.includes(type.toString())) {
                    return true;
                }
            }
        }
        let declaringStmt = arg.getDeclaringStmt();
        let backTrackCount = 0;
        while (declaringStmt && backTrackCount < 12) {
            if (this.isMultimediaCreateStmt(declaringStmt)) {
                return true;
            }
            if (declaringStmt instanceof ArkAssignStmt) {
                let leftOpType = declaringStmt.getLeftOp().getType().toString();
                let rightOp = declaringStmt.getRightOp();
                let rightOpType = rightOp.getType().toString();
                if (multimediaTypeList.includes(leftOpType) || multimediaTypeList.includes(rightOpType)) {
                    return true;
                }
                if (rightOp instanceof Local) {
                    declaringStmt = rightOp.getDeclaringStmt();
                } else {
                    break;
                }
            } else {
                break;
            }
            backTrackCount++;
        }
        if (useType === UseType.CALLBACK || useType === UseType.PROMISE_THEN) {
            let parameter = callbackMethod.getParameters().find((param) => {
                return param.getName() === arg.getName();
            });
            return parameter !== undefined;
        }
        return false;
    }

    private getInvokeCallbackMethod(arkFile: ArkFile, stmt: ArkInvokeStmt, methodName: string): ArkMethod | null {
        let arg: Value | null = null;
        if (methodName === 'createAVPlayer') {
            arg = stmt.getInvokeExpr().getArg(0);
        } else if (methodName === 'createAudioRenderer' || methodName === 'createAudioCapturer') {
            arg = stmt.getInvokeExpr().getArg(1);
        }
        if (!arg) {
            return null;
        }
        if (!(arg instanceof Local)) {
            return null;
        }
        let type = arg.getType();
        if (!(type instanceof FunctionType)) {
            return null;
        }
        return arkFile.getScene().getMethod(type.getMethodSignature());
    }

    private getFieldByBase(base: Local): ArkField | Local | null {
        let callMethod = base.getDeclaringStmt()?.getCfg()?.getDeclaringMethod();
        if (!callMethod) {
            return base;
        }
        if (!base.getName().includes('%')) {
            if (callMethod.getBody()?.getLocals().has(base.getName())) {
                return base;
            }
        } else {
            let declaringStmt = base.getDeclaringStmt();
            if (!declaringStmt || !(declaringStmt instanceof ArkAssignStmt)) {
                return null;
            }
            let rightOp = declaringStmt.getRightOp();
            if (rightOp instanceof ArkInstanceFieldRef) {
                return callMethod.getDeclaringArkClass().getField(rightOp.getFieldSignature());
            } else if (rightOp instanceof ArkStaticFieldRef) {
                let classSignature = rightOp.getFieldSignature().getDeclaringSignature();
                if (!(classSignature instanceof ClassSignature)) {
                    return null;
                }
                let targetClass = callMethod.getDeclaringArkFile().getScene().getClass(classSignature);
                if (!targetClass) {
                    return null;
                }
                return targetClass.getField(rightOp.getFieldSignature());
            } else {
                logger.debug('Temp variable unknown type, not implement.');
            }
        }
        return null;
    }

    public isDesignatedField(leftOp: Local, declaringStmt: ArkAssignStmt, fieldInfo: ArkField | null,
        localInfo: Local | null): boolean {
        if (leftOp.getName().includes('%')) {
            let rightOp = declaringStmt.getRightOp();
            if (fieldInfo && rightOp instanceof AbstractFieldRef &&
                rightOp.getFieldSignature().toString() === fieldInfo.getSignature().toString()) {
                return true;
            }
        } else {
            if (localInfo && localInfo.getName() === leftOp.getName()) {
                return true;
            }
            if (this.isMultimediaCreateStmt(declaringStmt)) {
                return true;
            }
        }
        return false;
    }

    private isResolveAssignToVariable(arkFile: ArkFile, stmt: ArkInvokeStmt, useType: UseType): boolean {
        const invoke = stmt.getInvokeExpr();
        if (!(invoke instanceof ArkPtrInvokeExpr)) {
            return false;
        }
        let fieldRef = invoke.getFuncPtrLocal();
        if (!(fieldRef instanceof AbstractFieldRef)) {
            return false;
        }
        let fieldName = fieldRef.getFieldName();
        if (fieldName !== 'resolve') {
            return false;
        }
        let declareSignature = fieldRef.getFieldSignature().getDeclaringSignature();
        let resolveClass = arkFile.getScene().getClass(declareSignature as ClassSignature);
        if (!resolveClass) {
            return false;
        }
        let superClassName = resolveClass.getSuperClassName();
        if (superClassName === 'Promise<T>') {
            return true;
        }
        return false;
    }

    private isInstanceAssignToVariable(callbackMethod: ArkMethod, stmt: ArkAssignStmt, useType: UseType): boolean {
        let rightOp = stmt.getRightOp();
        let rightType = rightOp.getType();
        if (this.typeProcess(useType, stmt, rightType, rightOp)) {
            return true;
        }
        if (!(rightOp instanceof Local)) {
            return false;
        }
        let hasInLocal = callbackMethod.getBody()?.getLocals().has(rightOp.getName());
        if (!hasInLocal) {
            return false;
        }
        if (!this.isArgTypeMultimedia(callbackMethod, rightOp, useType)) {
            logger.debug('Maybe include middle variables, not implement.');
        }
        if (!(rightType instanceof UnknownType) && multimediaTypeList.includes(rightType.toString())) {
            return true;
        }
        let leftOp = stmt.getLeftOp();
        let leftType = leftOp.getType();
        let parameters = callbackMethod.getParameters();
        if (parameters.length === 0) {
            return false;
        }
        let parameterType = parameters[parameters.length - 1].getType();
        if (parameterType instanceof UnknownType && rightType instanceof UnknownType && leftType instanceof UnknownType) {
            return true;
        } else if (!(parameterType instanceof UnknownType) || !(rightType instanceof UnknownType) || !(leftOp instanceof AbstractFieldRef)) {
            return false;
        }
        if (!(leftType instanceof UnionType)) {
            return multimediaTypeList.includes(leftType.toString());
        }
        for (let type of leftType.getTypes()) {
            if (multimediaTypeList.includes(type.toString())) {
                return true;
            }
        }
        return false;
    }

    private typeProcess(useType: UseType, stmt: ArkAssignStmt, rightType: Type, rightOp: Value): boolean {
        if (useType === UseType.PROMISE_AWIT) {
            if (this.isAwaitReturnStmt(stmt) && this.isMultimediaCreateStmt(stmt)) {
                return true;
            }
        }
        if (useType !== UseType.CALLBACK && useType !== UseType.PROMISE_THEN) {
            if (multimediaTypeList.includes(rightType.toString())) {
                return true;
            }
        }
        return false;
    }

    private isMultimediaCreateStmt(stmt: Stmt): boolean {
        let invoker = CheckerUtils.getInvokeExprFromStmt(stmt);
        if (!invoker) {
            return false;
        }
        const methodSignature = invoker.getMethodSignature();
        const methodSignatureStr = methodSignature.toString();
        return multimediaAPI8CreateSignList.includes(methodSignatureStr) ||
            multimediaInterruptSignList.includes(methodSignatureStr);
    }

    private isAudioInterruptSignature(arkFile: ArkFile, invoker: AbstractInvokeExpr): boolean {
        const methodSignature = invoker.getMethodSignature();
        const classSignatureStr = methodSignature.getDeclaringClassSignature().toString();
        if (!multimediaTypeList.includes(classSignatureStr)) {
            return false;
        }
        let methodName = methodSignature.getMethodSubSignature().getMethodName();
        if (methodName !== 'play') {
            return false;
        }
        let onArkMethod = arkFile.getScene().getMethod(methodSignature);
        if (!onArkMethod) {
            return false;
        }
        let declareSignatures = onArkMethod.getDeclareSignatures();
        if (!declareSignatures) {
            return false;
        }
        for (let declareSignature of declareSignatures) {
            let declareSignatureStr = declareSignature.toString();
            if (multimediaInterruptSignList.includes(declareSignatureStr)) {
                return true;
            }
        }
        return false;
    }

    private reportIssue(arkFile: ArkFile, stmt: Stmt, methodName: string): void {
        const text = stmt.getOriginalText();
        if (!text || text.length === 0) {
            logger.debug('Stmt text is empty.');
            return;
        }
        let methodNameIndex = text.indexOf(methodName);
        if (methodNameIndex === -1) {
            return;
        }
        const severity = this.rule.alert ?? this.metaData.severity;
        let originalPosition = stmt.getOriginPositionInfo();
        let lineNum = originalPosition.getLineNo();
        let startColum = originalPosition.getColNo() + methodNameIndex;
        let endColum = startColum + methodName.length - 1;
        let filePath = arkFile.getFilePath();
        let defects = new Defects(lineNum, startColum, endColum, this.metaData.description, severity, this.rule.ruleId, filePath,
            this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defects, undefined));
    }
}